#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <iconv.h>

#include <caml/mlvalues.h>
#include <caml/alloc.h>
#include <caml/memory.h>
#include <caml/fail.h>

/**
 * Convert a string from fromcode to tocode.
 */
value iconv_line_c(value line_str, value fromcode, value tocode) {
  int line_len;
  iconv_t converter;
  char *inbuf, *inptr;
  char *outbuf, *outptr;
  int inbytesleft, outbytesleft, outsize;
  size_t res;
  char *retstr;
  size_t retstrlen;
  int newoutsize;

  CAMLparam3(line_str, fromcode, tocode);

  if ((converter = 
       iconv_open((char *)tocode, (char *)fromcode)) == (iconv_t)(-1))
    caml_raise_constant(caml_named_value("Iconv_open_failed"));
  iconv(converter, NULL, NULL, NULL, NULL);
  
  inbytesleft = line_len = Wosize_val(line_str) * sizeof(value);
  outbytesleft = outsize = inbytesleft;

  if ((outbuf = (char *)malloc(sizeof(char)*outsize)) == NULL) {
    goto fail;
  }
  outptr = outbuf;
  inptr = inbuf = (char *)line_str;

  //printf("ICONV:1\n"); fflush(stdout);
  while (inbytesleft > 0) {
    //printf("ICONV: loop start\n"); fflush(stdout);
    res = iconv(converter, &inptr, &inbytesleft, &outptr, &outbytesleft);
    //printf("ICONV: iconv end\n"); fflush(stdout);
    if (res == (size_t)(-1)) {
      //printf("ICONV: error\n"); fflush(stdout);
      switch (errno) {
      case E2BIG:
        //printf("ICONV:E2BIG\n"); fflush(stdout);
        newoutsize = outsize<<1;
        if ((outbuf = (char *)realloc(outbuf, sizeof(char)*newoutsize)) == NULL)
          goto fail;
        outptr = outbuf+outsize-outbytesleft;
        outbytesleft += newoutsize-outsize;
        outsize = newoutsize;
        break;
      case EILSEQ: 
        //printf("ICONV:EILSEQ\n"); fflush(stdout);
      case EINVAL:
        //printf("ICONV:EINVAL\n"); fflush(stdout);
      default:
        goto fail;
      }
    }
    //printf("ICONV: loop end\n"); fflush(stdout);
  }
  //printf("ICONV:2\n"); fflush(stdout);

 done:
  iconv_close(converter);
  retstrlen = outsize-outbytesleft+1;
  if ((retstr = (char *)malloc(sizeof(char)*retstrlen)) == NULL)
    goto fail;
  strncpy(retstr, outbuf, retstrlen-1);
  retstr[retstrlen-1]='\0';
  //printf("ICONV:3\n"); fflush(stdout);
  CAMLreturn (caml_copy_string(retstr));

 fail:
  if (outbuf != NULL) {
    free(outbuf);
  }
  iconv_close(converter);
  caml_raise_constant(caml_named_value("Iconv_failed"));
}
